package com.dynamic.mybatis.core.parser.field;

import cn.hutool.core.date.DatePattern;
import com.dynamic.mybatis.core.datasource.DataSourceAttributesKey;
import com.dynamic.mybatis.core.enums.Type;
import com.dynamic.mybatis.core.metadata.MappedStatementFieldValidator;
import com.dynamic.mybatis.core.metadata.MappedStatementMetaField;
import com.dynamic.mybatis.core.metadata.MappedStatementTable;
import com.dynamic.mybatis.core.session.DynamicSqlSession;
import com.dynamic.mybatis.core.utils.StringUtils;

import java.math.BigDecimal;
import java.sql.*;
import java.util.*;

/**
 * @Description
 * @Author xs
 * @Date 2023/4/12 10:25
 */
public class DatabaseMetaDataHelper {
    private BigDecimal one = new BigDecimal(1);
    private BigDecimal two = new BigDecimal(2);

    private String primaryKey_key = "primaryKey";

    public List<MappedStatementTable> getDatabaseMeta(DynamicSqlSession dynamicSqlSession, String dataSourceKey, String... tableNames) {
        List<MappedStatementTable> list = new ArrayList<>();
        for (String tableName : tableNames) {
            try {
                list.add(getTransportDatabaseMeta(getDatabaseMeta(dynamicSqlSession, dataSourceKey, tableName), dynamicSqlSession, dataSourceKey));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return list;
    }


    public MappedStatementTable getTransportDatabaseMeta(DatabaseTable databaseTable, DynamicSqlSession dynamicSqlSession, String dataSourceKey) {
        MappedStatementTable table = new MappedStatementTable();
        table.setTableName(databaseTable.getTableName());
        table.setName(databaseTable.getRemarks());

        List<MappedStatementMetaField> fields = new ArrayList<>();

        Map<Object, Object> attrs = dynamicSqlSession.getDataSourceAttributes(dataSourceKey);
        attrs = attrs != null ? attrs : Collections.EMPTY_MAP;

        for (int i = 0; i < databaseTable.getColumns().size(); i++) {
            DatabaseTableColumn column = databaseTable.getColumns().get(i);
            MappedStatementMetaField field = new MappedStatementMetaField();
            field.setColumn(column.getActualColumnName());
            field.setField(StringUtils.underlineToCamel(column.getActualColumnName()));
            field.setVisible(Boolean.TRUE);
            field.setName(column.getRemarks());
            Type type = column.getType();
            if(type != null){
                field.setJavaTypeClass(type.getJavaType());
                field.setJavaType(type.getJavaType() != null ? type.getJavaType().getName(): null);
                field.setType(type.getJavaTypeAlias());
                field.setJdbcType(JDBCType.valueOf(type.getJdbcType()));
            }

            field.setAutoincrement(column.isAutoIncrement());
            field.setPrimaryKey("1".equals(column.getProperties().getProperty(primaryKey_key)));

            field.setLogicDelete(DataSourceAttributesKey.hasLogicDelete(field.getColumn(), attrs));
            field.setLogicDeleteValue(DataSourceAttributesKey.getLogicDeleteValue(attrs));
            field.setLogicNotDeleteValue(DataSourceAttributesKey.getLogicNotDeleteValue(attrs));
            field.setCreateBy(DataSourceAttributesKey.hasCreateBy(field.getColumn(), attrs));
            field.setCreateTime(DataSourceAttributesKey.hasCreateTime(field.getColumn(), attrs));
            field.setUpdateBy(DataSourceAttributesKey.hasUpdateBy(field.getColumn(), attrs));
            field.setUpdateTime(DataSourceAttributesKey.hasUpdateTime(field.getColumn(), attrs));

            MappedStatementFieldValidator validator = new MappedStatementFieldValidator();
            validator.setRequired(!column.isNullable());

            //默认为无符号
            if (type.getMin() != null && type.getMin() != null) {
                validator.setUnsigned(true);
                validator.setMin(0);
                validator.setMax(new BigDecimal(type.getMax().toString()).multiply(two).add(one));
            }
            if (Type.StandardType.VARCHAR.javaTypeAlias.equals(field.getType())) {
                validator.setMaxLength(column.getLength());
            } else {
                validator.setMaxLength(type.getLength());
            }
            if (field.getJdbcType() == JDBCType.TIMESTAMP) {
                validator.setDatePattern(DatePattern.NORM_DATETIME_PATTERN);
            } else if (field.getJdbcType() == JDBCType.DATE) {
                validator.setDatePattern(DatePattern.NORM_DATE_PATTERN);
            } else if (field.getJdbcType() == JDBCType.TIME) {
                validator.setDatePattern(DatePattern.NORM_TIME_PATTERN);
            }
            field.setValidator(validator);
            field.setSort((byte) i);
            fields.add(field);
        }

        table.setFields(fields);
        return table;
    }

    public Set<String> getTables(Connection connection) throws SQLException {
        DatabaseMetaData databaseMetaData = connection.getMetaData();
        String localCatalog = connection.getCatalog();
        String localSchema = connection.getSchema();
        ResultSet rs = databaseMetaData.getTables(localCatalog, localSchema, "%", new String[]{"TABLE"});
        Set<String> set = new HashSet<>();
        while (rs.next()) {
            set.add(rs.getString("TABLE_NAME "));
        }
        return set;
    }


    public DatabaseTable getDatabaseMeta(DynamicSqlSession dynamicSqlSession, String dataSourceKey, String tableName) throws SQLException {
        Connection connection = dynamicSqlSession.getConnectionByDataSourceKey(dataSourceKey);

        DatabaseMetaData databaseMetaData = connection.getMetaData();
        String localCatalog = connection.getCatalog();
        String localSchema = connection.getSchema();
        String localTableName = tableName;
        ResultSet rsTables = databaseMetaData.getTables(localCatalog, localSchema, tableName, new String[]{"TABLE"});
        String remarks = "";
        while (rsTables.next()) {
            remarks = rsTables.getString("REMARKS");
        }
        rsTables.close();

        ResultSet resultSet = databaseMetaData.getPrimaryKeys(localCatalog, localSchema, localTableName);
        Set<String> primaryKeyColumns = new HashSet<>();

        while (resultSet.next()) {
            primaryKeyColumns.add(resultSet.getString("COLUMN_NAME"));
        }
        resultSet.close();

        List<DatabaseTableColumn> columns = new ArrayList<>();
        ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema, localTableName, "%");

        DatabaseTable databaseTable = null;
        while (rs.next()) {
            DatabaseTableColumn introspectedColumn = new DatabaseTableColumn();

            introspectedColumn.setActualTypeName(rs.getString("TYPE_NAME")); //$NON-NLS-1$
            introspectedColumn.setLength(rs.getInt("COLUMN_SIZE")); //$NON-NLS-1$
            introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME")); //$NON-NLS-1$
            introspectedColumn.setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); //$NON-NLS-1$
            introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS")); //$NON-NLS-1$
            introspectedColumn.setRemarks(rs.getString("REMARKS")); //$NON-NLS-1$
            introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF")); //$NON-NLS-1$

            introspectedColumn.getProperties().setProperty(primaryKey_key, primaryKeyColumns.contains(introspectedColumn.getActualColumnName()) ? "1" : "0");

            introspectedColumn.setAutoIncrement("YES".equals(rs.getString("IS_AUTOINCREMENT"))); //$NON-NLS-1$ //$NON-NLS-2$

            introspectedColumn.setGeneratedColumn("YES".equals(rs.getString("IS_GENERATEDCOLUMN"))); //$NON-NLS-1$ //$NON-NLS-2$

            introspectedColumn.setJavaProperty(StringUtils.underlineToCamel(introspectedColumn.getActualColumnName()));

            Type type =  Type.getTypeByJdbcType(rs.getInt("DATA_TYPE"));
            introspectedColumn.setType(type);
            if (databaseTable == null) {
                databaseTable = new DatabaseTable(
                        rs.getString("TABLE_CAT"), //$NON-NLS-1$
                        rs.getString("TABLE_SCHEM"), //$NON-NLS-1$
                        rs.getString("TABLE_NAME")); //$NON-NLS-1$
            }
            columns.add(introspectedColumn);
        }
        rs.close();
        connection.close();
        databaseTable.setRemarks(remarks);
        databaseTable.setColumns(columns);
        return databaseTable;
    }

    public static class DatabaseTable{
        List<DatabaseTableColumn> columns;
        String catalog;
        String schema;
        String tableName;
        String remarks;

        public DatabaseTable( String catalog, String schema, String tableName) {
            this.catalog = catalog;
            this.schema = schema;
            this.tableName = tableName;
        }

        public DatabaseTable(String catalog, String schema, String tableName, String remarks, List<DatabaseTableColumn> columns) {
            this.catalog = catalog;
            this.schema = schema;
            this.tableName = tableName;
            this.columns = columns;
            this.remarks = remarks;
        }

        public List<DatabaseTableColumn> getColumns() {
            return columns;
        }

        public void setColumns(List<DatabaseTableColumn> columns) {
            this.columns = columns;
        }

        public String getRemarks() {
            return remarks;
        }

        public void setRemarks(String remarks) {
            this.remarks = remarks;
        }

        public String getCatalog() {
            return catalog;
        }

        public void setCatalog(String catalog) {
            this.catalog = catalog;
        }

        public String getSchema() {
            return schema;
        }

        public void setSchema(String schema) {
            this.schema = schema;
        }

        public String getTableName() {
            return tableName;
        }

        public void setTableName(String tableName) {
            this.tableName = tableName;
        }
    }

    class DatabaseTableColumn{
        protected String actualColumnName;

        protected Type type;

        protected String actualTypeName;

        protected boolean nullable;
        protected int length;
        protected int scale;
        protected boolean identity;
        protected boolean isSequenceColumn;
        protected String javaProperty;
/*      protected FullyQualifiedJavaType fullyQualifiedJavaType;*/
        protected String tableAlias;
        protected String typeHandler;
        protected boolean isColumnNameDelimited;
        protected final Properties properties = new Properties();
        protected String remarks;
        protected String defaultValue;
        protected boolean isAutoIncrement;
        protected boolean isGeneratedColumn;
        protected boolean isGeneratedAlways;

        public String getActualColumnName() {
            return actualColumnName;
        }

        public void setActualColumnName(String actualColumnName) {
            this.actualColumnName = actualColumnName;
        }


        public String getActualTypeName() {
            return actualTypeName;
        }

        public void setActualTypeName(String actualTypeName) {
            this.actualTypeName = actualTypeName;
        }



        public boolean isNullable() {
            return nullable;
        }

        public void setNullable(boolean nullable) {
            this.nullable = nullable;
        }

        public int getLength() {
            return length;
        }

        public void setLength(int length) {
            this.length = length;
        }

        public int getScale() {
            return scale;
        }

        public void setScale(int scale) {
            this.scale = scale;
        }

        public boolean isIdentity() {
            return identity;
        }

        public void setIdentity(boolean identity) {
            this.identity = identity;
        }

        public boolean isSequenceColumn() {
            return isSequenceColumn;
        }

        public void setSequenceColumn(boolean sequenceColumn) {
            isSequenceColumn = sequenceColumn;
        }

        public String getJavaProperty() {
            return javaProperty;
        }

        public void setJavaProperty(String javaProperty) {
            this.javaProperty = javaProperty;
        }

        public String getTableAlias() {
            return tableAlias;
        }

        public void setTableAlias(String tableAlias) {
            this.tableAlias = tableAlias;
        }

        public String getTypeHandler() {
            return typeHandler;
        }

        public void setTypeHandler(String typeHandler) {
            this.typeHandler = typeHandler;
        }

        public boolean isColumnNameDelimited() {
            return isColumnNameDelimited;
        }

        public void setColumnNameDelimited(boolean columnNameDelimited) {
            isColumnNameDelimited = columnNameDelimited;
        }

        public Properties getProperties() {
            return properties;
        }

        public String getRemarks() {
            return remarks;
        }

        public void setRemarks(String remarks) {
            this.remarks = remarks;
        }

        public String getDefaultValue() {
            return defaultValue;
        }

        public void setDefaultValue(String defaultValue) {
            this.defaultValue = defaultValue;
        }

        public boolean isAutoIncrement() {
            return isAutoIncrement;
        }

        public void setAutoIncrement(boolean autoIncrement) {
            isAutoIncrement = autoIncrement;
        }

        public boolean isGeneratedColumn() {
            return isGeneratedColumn;
        }

        public void setGeneratedColumn(boolean generatedColumn) {
            isGeneratedColumn = generatedColumn;
        }

        public boolean isGeneratedAlways() {
            return isGeneratedAlways;
        }

        public void setGeneratedAlways(boolean generatedAlways) {
            isGeneratedAlways = generatedAlways;
        }

        public Type getType() {
            return type;
        }

        public void setType(Type type) {
            this.type = type;
        }
    }

}